/*
 * ServerContext.java
 *
 * $Id: ServerContext.java,v 1.8 2009-04-07 15:11:22 juancarlos Exp $
 */
package org.ceteca.explica.server;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.log4j.Logger;
import org.ceteca.explica.client.gui.ImageUtils;
import org.ceteca.explica.core.HibernateEntityResolver;
import org.ceteca.explica.core.InterfaceErrors;
import org.ceteca.explica.core.InterfaceExplica;
import org.ceteca.explica.core.util.LoggerFactory;
import org.ceteca.explica.core.util.OperationResult;
import org.ceteca.explica.core.util.ResultMessageCatalog;
import org.ceteca.explica.server.util.db.HibernateUtil;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.widgets.Display;
import org.hibernate.cfg.Configuration;
import org.w3c.dom.Document;
import org.xnap.commons.i18n.I18n;
import org.xnap.commons.i18n.I18nFactory;

/**
 * This class lays on the server side of the Explica application and works
 * as an interface for initializing the server generic instances used
 * by the server objects. It also gives access to the generic properties
 * and systems, as for example, logging, internationalization, etc. 
 * <br/>
 * Responsibilities:
 * <li>
 * 	<ul> Initialize server logging system. </ul>
 *  <ul> Initialize server internationalization system. </ul>
 *  <ul> Build the catalog of messages used by the system. </ul>
 *  <ul> Give accessors for all these general objects.  </ul>
 * </li>
 */
public class ServerContext {
	/**
	 * Instance of the internationalization system used by the server objects.
	 */
	private I18n i18n;
	/**
	 * Catalog of result and error messages used by the server objects.
	 */
	private ResultMessageCatalog msgCatalog;
	/**
	 * Instance of the logging system used by the server objects.
	 */
	private Logger logger;
	
	/** Unique instance of the ServerContext class. Singleton pattern */
	private static ServerContext instance;
	
	/** 
	 * Returns the unique instance of the ServerContext class.
	 * Implements the singleton dessign pattern.
	 * @return ServerContext, unique instance of ServerContext.
	 */
	public static final ServerContext getInstance() {
		if (instance == null)
			instance = new ServerContext();
	
		return instance;
	}
	
	/**
	 * Default ServerContext constructor.
	 */
	private ServerContext() {
		this.i18n = null;
		this.msgCatalog = null;
		this.logger = null;
	}

	/**
	 * Inits the server context and builds all the generic objects.
	 */
	public void initServerContext() {
		// Init the logger service
		System.out.println(this.getClass().getResource(InterfaceExplica.EXPLICA_LOG_CONFIG_FILE).getPath());
		LoggerFactory.initLogger(
				InterfaceExplica.EXPLICA_LOG_DIR,
				this.getClass().getResourceAsStream(InterfaceExplica.EXPLICA_LOG_CONFIG_FILE));
		this.logger = LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY);
		System.out.println(this.getClass().getResource(InterfaceExplica.EXPLICA_MESSAGES_PROPERTIES_FILE).getPath());
		// Get the internationalization object instance
		this.i18n = I18nFactory.getI18n(ServerManager.class);
		

		
		// Build the catalog of result messages defined for the explica core application module.
		this.msgCatalog = new ResultMessageCatalog(
				this.getClass().getResourceAsStream(InterfaceExplica.EXPLICA_MESSAGES_PROPERTIES_FILE), 
				this.i18n, LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY)
		);

		// Add the messages defined for the WAREHOUSE and PARTNERS application modules to the catalog of result messages.
		try {
			System.out.println(this.getClass().getResource("/org/ceteca/explica/core/warehouse/warehouse-messages.properties").getPath());
		}
		catch (Exception ex) {
			ex.printStackTrace();
		}
		this.msgCatalog.addResultMessages(this.getClass().getResourceAsStream("/org/ceteca/explica/core/warehouse/warehouse-messages.properties"));
		this.msgCatalog.addResultMessages(this.getClass().getResourceAsStream("/org/ceteca/explica/core/partners/partners-messages.properties"));
		
		// Print the loaded result messages list.
		//System.out.println("-------------- Catalogo de mensajes -------------------");
		//this.msgCatalog.printMessages();
		//System.out.println("-------------------------------------------------------");
		
		// Load the Hibernate Configuration
		URL url = this.getClass().getResource("/org/ceteca/explica/core/hibernate.cfg.xml");
		this.logger.info("Hibernate configuration file found at: " + (url!=null?url.getPath():"NOT FOUND"));
		HibernateUtil.configHibernate(this.getClass().getResourceAsStream("/org/ceteca/explica/core/hibernate.cfg.xml"));
		this.logger.info("Hibernate configuration completed!");		
	}

	/**
	 * Gets the logger instance used by the server objects for system logging messages.
	 * @return Logger, Explica server logger instance.
	 */
	public Logger getLogger() {
		return this.logger;
	}
	
	/**
	 * Gets the logger instance corresponding to the specified category.
	 * @return Logger, category logger instance.
	 */
	public Logger getLogger(String category) {
		return LoggerFactory.getLogger(category);
	}

	/**
	 * Gets the internationalization object used by the server objects
	 * for translating messages. 
	 * @return I18n, Explica server internationalization instance.
	 */
	public I18n getI18n() {
		return i18n;
	}

	/**
	 * Gets the catalog of result and error messages showed to the user
	 * as the result of the operations made by the server objects. 
	 * @return ResultMessageCatalog, Explica server messages catalog.
	 */
	public ResultMessageCatalog getMsgCatalog() {
		return msgCatalog;
	}
	
	/**
	 * Loads an image from the server resources path.
	 * @param imageFile String, filename of the image file.
	 * @return byte[], renderized loaded image.
	 */
	public byte[] loadImage(String imageFile) {
		this.logger.debug("Loading image: " + imageFile);
		if (imageFile != null && imageFile.length() > 0) {
			try {
				File f2 = new File("hola");
				this.logger.info("Ruta base: " + f2.getAbsolutePath());
				
				File f = new File("resources" + File.separator + "appImages" + File.separator + imageFile);
				this.logger.info("Ruta completa: " + f.getAbsolutePath() + " - " + f.exists());
				if (!f.exists() || !f.canRead()) {
					this.logger.error(String.format("Error loading image (%s). The image file cannot be found or doesn't have necessary permissions to access it!", f.getAbsolutePath()));
					return null;
				}
				
				// Open an input stream to read the file contents
				FileInputStream is = new FileInputStream(f);
		        
		        // Get the size of the file
		        long length = f.length();
		    
		        if (length > Integer.MAX_VALUE) {
		        	this.logger.error(String.format("Error loading image (%s). The image file is too large!", f.getAbsolutePath()));
		        	return null;
		        }
		    
		        // Create the byte array to hold the data
		        byte[] bytes = new byte[(int)length];
		    
		        // Read in the bytes
		        int offset = 0;
		        int numRead = 0;
		        while (offset < bytes.length
		               && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
		            offset += numRead;
		        }
		    
		        // Ensure all the bytes have been read in
		        if (offset < bytes.length) {
		            this.logger.error("Could not completely read file " + imageFile);
		            return null;
		        }
		    
		        // Close the input stream and return bytes
		        is.close();
		        
		        return bytes;
			}
			catch (Exception ex) {
				this.logger.error(String.format("Error loading image. The image file (%s) couldn't be loaded!", imageFile), ex);
				return null;
			}
		}
		else {
			this.logger.error(String.format("Error loading image (%s). The image filename is null or empty!", imageFile));
			return null;
		}
	}
	
	public ImageData getImage(String imageFile) {
		this.logger.debug("Loading image: " + imageFile);
		ImageData imgData = null;
		if (imageFile != null && imageFile.length() > 0) {
			try {
				File f = new File("resources" + File.separator + "appImages" + File.separator + imageFile);
				if (!f.exists() || !f.canRead()) {
					this.logger.error(String.format("Error loading image (%s). The image file cannot be found or doesn't have necessary permissions to access it!", f.getAbsolutePath()));
					imgData = null;
				}
				else {
					// Open an input stream to read the file contents
					FileInputStream is = new FileInputStream(f);
					Image img = new Image(Display.getCurrent(), is);
					imgData = img.getImageData();
		    
			        // Close the input stream and return bytes
			        is.close();
				}
			}
			catch (Exception ex) {
				this.logger.error(String.format("Error loading image. The image file (%s) couldn't be loaded!", imageFile), ex);
				imgData = null;
			}
		}
		else {
			this.logger.error(String.format("Error loading image (%s). The image filename is null or empty!", imageFile));
			imgData = null;
		}
		
		return imgData;
	}
	
	public OperationResult saveImage(String imageFile, byte[] contents) {
		OperationResult result = new OperationResult();
		
		this.logger.debug("Adding image: " + imageFile);
		if (imageFile != null && imageFile.length() > 0) {
			try {
				File f = new File("resources" + File.separator + "appImages" + File.separator + imageFile);
				if (!f.canWrite()) {
					this.logger.error(String.format("Error adding image (%s). The image file cannot be found or doesn't have necessary permissions to access it!", f.getAbsolutePath()));
					result.addResultMessage(OperationResult.RESULT_ERROR, 
							this.getMsgCatalog().newResultMessage(InterfaceErrors.ERR_IMAGE_SAVE_NO_WRITE_PERMISSIONS, f.getAbsolutePath(), null));
				}
				else {
					// Open an input stream to read the file contents
					FileOutputStream os = new FileOutputStream(f);
					
					// Write the contents to the file
					os.write(contents);
		    
			        // Close the input stream
			        os.close();
			        
			        result.addResultMessage(OperationResult.RESULT_OK, 
							this.getMsgCatalog().newResultMessage(InterfaceErrors.MSG_IMAGE_SAVE_OK, imageFile, null));
				}
			}
			catch (Exception ex) {
				this.logger.error(String.format("Error saving image. The image file (%s) couldn't be saved!", imageFile), ex);
				result.addResultMessage(OperationResult.RESULT_ERROR, 
						this.getMsgCatalog().newResultMessage(InterfaceErrors.ERR_IMAGE_SAVE_ERROR, imageFile, null));
			}
		}
		else {
			this.logger.error(String.format("Error saving image (%s). The image filename is null or empty!", imageFile));
			result.addResultMessage(OperationResult.RESULT_ERROR, 
					this.getMsgCatalog().newResultMessage(InterfaceErrors.ERR_IMAGE_SAVE_ERROR, imageFile, null));
		}
		
		return result;
	}
	
	public OperationResult delImage(String imageFile) {
		OperationResult result = new OperationResult();
		
		this.logger.debug("Deleting image: " + imageFile);
		if (imageFile != null && imageFile.length() > 0) {
			try {
				File f = new File("resources" + File.separator + "appImages" + File.separator + imageFile);
				if (!f.canWrite()) {
					this.logger.error(String.format("Error deleting image (%s). The image file cannot be found or doesn't have necessary permissions to access it!", f.getAbsolutePath()));
					result.addResultMessage(OperationResult.RESULT_ERROR, 
							this.getMsgCatalog().newResultMessage(InterfaceErrors.ERR_IMAGE_DELETE_NO_WRITE_PERMISSIONS, f.getAbsolutePath(), null));
				}
				else {
					if (f.delete()) {
						result.addResultMessage(OperationResult.RESULT_OK, 
								this.getMsgCatalog().newResultMessage(InterfaceErrors.MSG_IMAGE_DELETE_OK, imageFile, null));						
					}
					else {
						result.addResultMessage(OperationResult.RESULT_ERROR, 
								this.getMsgCatalog().newResultMessage(InterfaceErrors.ERR_IMAGE_DELETE_ERROR, imageFile, null));
					}
				}
			}
			catch (Exception ex) {
				this.logger.error(String.format("Error deleting image. The image file (%s) couldn't be deleted!", imageFile), ex);
				result.addResultMessage(OperationResult.RESULT_ERROR, 
						this.getMsgCatalog().newResultMessage(InterfaceErrors.ERR_IMAGE_SAVE_ERROR, ex, imageFile, (Object)null));
			}
		}
		else {
			this.logger.error(String.format("Error deleting image (%s). The image filename is null or empty!", imageFile));
			result.addResultMessage(OperationResult.RESULT_ERROR, 
					this.getMsgCatalog().newResultMessage(InterfaceErrors.ERR_IMAGE_DELETE_ERROR, imageFile, null));
		}
		
		return result;
	}
	
	/**
	 * Returns tag Id assigned to CVS source file.
	 */
	public static String getRevision() {
		return "$Id: ServerContext.java,v 1.8 2009-04-07 15:11:22 juancarlos Exp $";
	}
}
